#include "ECS/System/PUnlitMaterialSystem.h"
#include "ECS/Component/Material/PUnlitMaterialComponent.h"
#include "ECS/System/PBaseMaterialSystem.h"

#include "PiccoloFileUtil.h"

#include "ECS/Component/PLookAtCameraComponent.h"
#include "ECS/PEntity.h"

#include "PiccoloGeometryUtil.h"

#include "Graphics/PiccoloVKPipeline.h"
#include "Graphics/PiccoloVKRenderPass.h"
#include "Graphics/PiccoloVKCommandBuffer.h"

#include "PiccoloEntryPoint.h"
#include "Render/PiccoloRenderTarget.h"
#include "Render/PiccoloMesh.h"
#include "Render/PiccoloRenderer.h"
#include "Render/PMaterial.h"

#include "PEventTesting.h"

namespace Piccolo
{
    class SandBoxApp : public PiccoloApplication
    {
    protected:
        void OnConfiguration(AppSettings *appSettings) override
        {
            appSettings->width = 1360;
            appSettings->height = 768;
            appSettings->title = "SandBox";
        }

        void OnInit() override
        {
            PiccoloRenderContext* renderContext = GetAppContext()->renderContext;
            PiccoloVKDevice* device = renderContext->GetDevice();
            PiccoloVKSwapchain* swapchain = renderContext->GetSwapchain();

            // 创建颜色附件和深度附件
            std::vector<Attachment> attachments =
            {
                {
                    .format = swapchain->GetSurfaceInfo().surfaceFormat.format,
                    .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
                    .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
                    .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
                    .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
                    .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
                    .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
                    .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
                },
                {
                    .format = device->GetSettings().depthFormat,
                    .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
                    .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
                    .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
                    .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
                    .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
                    .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
                    .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
                }
            };

            std::vector<RenderSubPass> subPasses =
            {
                {
                    .colorAttachments = { 0 },
                    .depthStencilAttachments = { 1 },
                    .sampleCount = VK_SAMPLE_COUNT_4_BIT
                }
            };
            mRenderPass = std::make_shared<PiccoloVKRenderPass>(device, attachments, subPasses);
            mRenderTarget = std::make_shared<PiccoloRenderTarget>(mRenderPass.get());
            mRenderTarget->SetColorClearValue({ 0.1f, 0.2f, 0.3f, 1.f });
            mRenderTarget->SetDepthStencilClearValue({ 1, 0 });
            // 给这个render target添加一个材质系统
            mRenderTarget->AddMaterialSystem<PBaseMaterialSystem>();
            mRenderTarget->AddMaterialSystem<PUnlitMaterialSystem>();

            mRenderer = std::make_shared<PiccoloRenderer>();
            mCommandBuffers = device->GetDefaultCommandPool()->AllocateCommandBuffer(swapchain->GetImages().size());

            // Event
            mEventTesting = std::make_shared<PEventTesting>();
            mObserver = std::make_shared<PEventObserver>();
            // 鼠标滚轮改变摄像机半径
            mObserver->OnEvent<PMouseScrollEvent>([this](const PMouseScrollEvent& event)
            {
                PEntity* camera = mRenderTarget->GetCamera();
                if (PEntity::HasComponent<PLookAtCameraComponent>(camera))
                {
                    auto& cameraComponent = camera->GetComponent<PLookAtCameraComponent>();
                    float radius = cameraComponent.GetRadius() + event.mYOffset * -0.3f;
                    if (radius < 0.1f) radius = 0.1f;
                    cameraComponent.SetRadius(radius);
                }
            });

            std::vector<PiccoloVertex> vertices;
            std::vector<uint32_t> indices;
            PiccoloGeometryUtil::CreateCube(-0.1f, 0.1f, -0.1f, 0.1f, -0.1f, 0.1f, vertices, indices);
            mCubeMesh = std::make_shared<PiccoloMesh>(vertices, indices);


            mDefaultSampler = std::make_shared<PiccoloSampler>(VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT);

            RGBAColor whiteColor = { 255, 255, 255, 255 };
            RGBAColor blackColor = { 0, 0, 0, 0 };
            RGBAColor multiColor[4] = { {255, 255, 255, 255}, {192, 192, 192, 255}, {192, 192, 192, 255}, {255, 255, 255, 255} };

            mWhiteTexture = std::make_shared<PiccoloTexture>(1, 1, &whiteColor);
            mBlackTexture = std::make_shared<PiccoloTexture>(1, 1, &blackColor);
            mMultiPixelTexture = std::make_shared<PiccoloTexture>(2, 2, multiColor);
            mFileTexture = std::make_shared<PiccoloTexture>(PICCOLO_RES_TEXTURE_DIR"R-C.jpeg");
        }

        void OnSceneInit(PScene *scene) override
        {
            PEntity* camera = scene->CreateEntity("Editor Camera");
            camera->AddComponent<PLookAtCameraComponent>();
            mRenderTarget->SetCamera(camera);


            auto baseMaterial0 = PMaterialFactory::GetInstance()->CreateMaterial<PBaseMaterial>();
            auto baseMaterial1 = PMaterialFactory::GetInstance()->CreateMaterial<PBaseMaterial>();
            baseMaterial1->colorType = COLOR_TYPE_TEXCOORD;

            float x = -2.f;
            for (int i = 0; i < mSmallCubeCount.x; i++, x += 0.5f)
            {
                float y = -2.f;
                for (int j = 0; j < mSmallCubeCount.y; j++, y += 0.5f)
                {
                    float z = -2.f;
                    for (int k = 0; k < mSmallCubeCount.z; k++, z += 0.5f)
                    {
                        PEntity* cube = scene->CreateEntity("Cube 0");
                        auto& transformComponent = cube->GetComponent<PTransformComponent>();
                        transformComponent.position = { x, y, z };

                        mSmallCubes.push_back(cube);
                    }
                }
            }
        }

        void OnSceneDestroy(PScene *scene) override
        {

        }

        void OnUpdate(float deltaTime) override
        {
            uint64_t frameIndex = GetFrameIndex();
            PiccoloTexture* textures[] = { mWhiteTexture.get(), mBlackTexture.get(), mMultiPixelTexture.get(), mFileTexture.get() };
            if (frameIndex % 2 == 0 && mUnlitMaterials.size() < mSmallCubes.size())
            {
                auto material = PMaterialFactory::GetInstance()->CreateMaterial<PUnlitMaterial>();
                material->SetBaseColor0(glm::linearRand(glm::vec3(0.f, 0.f, 0.f), glm::vec3(1.f, 1.f, 1.f)));
                material->SetBaseColor1(glm::linearRand(glm::vec3(0.f, 0.f, 0.f), glm::vec3(1.f, 1.f, 1.f)));
                material->SetTextureView(UNLIT_MAT_BASE_COLOR_0, textures[glm::linearRand(0, (int)ARRAY_SIZE(textures) - 1)], mDefaultSampler.get());
                material->SetTextureView(UNLIT_MAT_BASE_COLOR_1, textures[glm::linearRand(0, (int)ARRAY_SIZE(textures) - 1)], mDefaultSampler.get());
                material->UpdateTextureViewEnable(UNLIT_MAT_BASE_COLOR_0, glm::linearRand(0, 1));
                material->UpdateTextureViewEnable(UNLIT_MAT_BASE_COLOR_1, glm::linearRand(0, 1));
                material->SetMixValue(glm::linearRand(0.1f, 0.8f));

                uint32_t cubeIndex = mUnlitMaterials.size();
                if (!PEntity::HasComponent<PUnlitMaterialComponent>(mSmallCubes[cubeIndex]))
                {
                    mSmallCubes[cubeIndex]->AddComponent<PUnlitMaterialComponent>();
                }
                auto& materialComponent = mSmallCubes[cubeIndex]->GetComponent<PUnlitMaterialComponent>();
                materialComponent.AddMesh(mCubeMesh.get(), material);

                mUnlitMaterials.push_back(material);
                LOG_D("Unlit Material Count: {0}", mUnlitMaterials.size());
            }

            if (frameIndex % 20 == 0 && !mUnlitMaterials.empty())
            {
                mUnlitMaterials[0]->SetMixValue(glm::linearRand(0.f, 1.f));
                mUnlitMaterials[0]->SetTextureView(UNLIT_MAT_BASE_COLOR_0, textures[glm::linearRand(0, (int)ARRAY_SIZE(textures) - 1)], mDefaultSampler.get());
            }

            PEntity* camera = mRenderTarget->GetCamera();
            if (PEntity::HasComponent<PLookAtCameraComponent>(camera))
            {
                if (!mWindow->IsMouseDown())
                {
                    bFirstMouseDrag = true;
                    return;
                }

                glm::vec2 mousePos;
                mWindow->GetMousePos(mousePos);
                glm::vec2 mousePosDelta = { mLastMousePos.x - mousePos.x, mLastMousePos.y - mousePos.y };
                mLastMousePos = mousePos;

                if (abs(mousePosDelta.x) > 0.1f || abs(mousePosDelta.y) > 0.1f)
                {
                    if (bFirstMouseDrag)
                    {
                        bFirstMouseDrag = false;
                    }
                    else
                    {
                        auto& transformComponent = camera->GetComponent<PTransformComponent>();
                        float yaw = transformComponent.rotation.x;
                        float pitch = transformComponent.rotation.y;

                        yaw += mousePosDelta.x * mMouseSensitivity;
                        pitch += mousePosDelta.y * mMouseSensitivity;
                        if (pitch > 89.f) pitch = 89.f;
                        if (pitch < -89.f) pitch = -89.f;

                        transformComponent.rotation.x = yaw;
                        transformComponent.rotation.y = pitch;
                    }
                }
            }
        }

        void OnRender() override
        {
            PiccoloRenderContext* renderContext = GetAppContext()->renderContext;
            PiccoloVKDevice* device = renderContext->GetDevice();
            PiccoloVKSwapchain* swapchain = renderContext->GetSwapchain();

            int32_t imageIndex;
            if (mRenderer->Begin(&imageIndex))
            {
                mRenderTarget->SetExtent({ swapchain->GetWidth(), swapchain->GetHeight() });
            }
            VkCommandBuffer cmdBuffer = mCommandBuffers[imageIndex];
            PiccoloVKCommandPool::BeginCommandBuffer(cmdBuffer);

            mRenderTarget->Begin(cmdBuffer);
            mRenderTarget->RenderMaterialSystemList(cmdBuffer);
            mRenderTarget->End(cmdBuffer);

            PiccoloVKCommandPool::EndCommandBuffer(cmdBuffer);
            if (mRenderer->End(imageIndex, { cmdBuffer }))
            {
                mRenderTarget->SetExtent({ swapchain->GetWidth(), swapchain->GetHeight() });
            }
        }

        void OnDestroy() override
        {
            PiccoloRenderContext* renderContext = GetAppContext()->renderContext;
            PiccoloVKDevice* device = renderContext->GetDevice();
            vkDeviceWaitIdle(device->GetLogicDevice());

            mDefaultSampler.reset();
            mWhiteTexture.reset();
            mBlackTexture.reset();
            mMultiPixelTexture.reset();
            mFileTexture.reset();

            mCubeMesh.reset();
            mCommandBuffers.clear();
            mRenderTarget.reset();
            mRenderPass.reset();
            mRenderer.reset();
        }

    private:
        std::shared_ptr<PiccoloVKRenderPass> mRenderPass;
        std::shared_ptr<PiccoloRenderTarget> mRenderTarget;
        std::shared_ptr<PiccoloRenderer> mRenderer;

        std::vector<VkCommandBuffer> mCommandBuffers;
        std::shared_ptr<PiccoloMesh> mCubeMesh;

        std::shared_ptr<PiccoloTexture> mWhiteTexture;
        std::shared_ptr<PiccoloTexture> mBlackTexture;
        std::shared_ptr<PiccoloTexture> mMultiPixelTexture;
        std::shared_ptr<PiccoloTexture> mFileTexture;
        std::shared_ptr<PiccoloSampler> mDefaultSampler;

        glm::ivec3 mSmallCubeCount { 10, 10, 10 };

        std::vector<PEntity*> mSmallCubes;
        std::vector<PUnlitMaterial*> mUnlitMaterials;

        std::shared_ptr<PEventTesting> mEventTesting;
        std::shared_ptr<PEventObserver> mObserver;

        bool bFirstMouseDrag = true;
        glm::vec2 mLastMousePos;
        float mMouseSensitivity = 0.25f;
    };
}

Piccolo::PiccoloApplication* CreateApplicationEntryPoint()
{
    return new Piccolo::SandBoxApp();
}